package com.atguigu.tingshu.user.service.impl;

import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.api.WxMaUserService;
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
import com.alibaba.fastjson.JSONObject;
import com.atguigu.tingshu.album.client.AlbumInfoFeignClient;
import com.atguigu.tingshu.common.constant.PublicConstant;
import com.atguigu.tingshu.common.constant.RedisConstant;
import com.atguigu.tingshu.common.constant.SystemConstant;
import com.atguigu.tingshu.common.execption.GuiguException;
import com.atguigu.tingshu.common.rabbit.constant.MqConst;
import com.atguigu.tingshu.common.rabbit.service.RabbitService;
import com.atguigu.tingshu.common.result.Result;
import com.atguigu.tingshu.common.result.ResultCodeEnum;
import com.atguigu.tingshu.common.util.AuthContextHolder;
import com.atguigu.tingshu.common.util.MongoUtil;
import com.atguigu.tingshu.model.user.*;
import com.atguigu.tingshu.user.mapper.UserInfoMapper;
import com.atguigu.tingshu.user.mapper.UserPaidAlbumMapper;
import com.atguigu.tingshu.user.mapper.UserPaidTrackMapper;
import com.atguigu.tingshu.user.service.UserInfoService;
import com.atguigu.tingshu.vo.album.TrackListVo;
import com.atguigu.tingshu.vo.album.TrackStatMqVo;
import com.atguigu.tingshu.vo.user.UserCollectVo;
import com.atguigu.tingshu.vo.user.UserInfoVo;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import io.micrometer.common.util.StringUtils;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.error.WxErrorException;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.jwt.Jwt;
import org.springframework.security.jwt.JwtHelper;
import org.springframework.security.jwt.crypto.sign.RsaSigner;
import org.springframework.security.jwt.crypto.sign.RsaVerifier;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo> implements UserInfoService {

    @Autowired
    private UserInfoMapper userInfoMapper;
    @Autowired
    private WxMaService wxMaService;
    @Autowired
    private RsaSigner rsaSigner;
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired
    private RabbitService rabbitService;
    @Autowired
    private UserPaidTrackMapper userPaidTrackMapper;
    @Autowired
    private UserPaidAlbumMapper userPaidAlbumMapper;
    @Autowired
    private MongoTemplate mongoTemplate;
    @Autowired
    private AlbumInfoFeignClient albumInfoFeignClient;

    @Override
    public Map<String, Object> wxLogin(String code) {
        String openId = "";

        //登录

        //判断code是否存在
        if (StringUtils.isEmpty(code)) {
            throw new GuiguException(201, "code码不存在,登陆失败");
        }
        // 2.获取微信登录的客户端
        //WxMaUserService userService = wxMaService.getUserService();
        WxMaUserService userService = wxMaService.getUserService();

        try {

            //WxMaJscode2SessionResult sessionInfo = userService.getSessionInfo(code);
            WxMaJscode2SessionResult sessionInfo = userService.getSessionInfo(code);

            //openId = sessionInfo.getOpenid();
            openId = sessionInfo.getOpenid();
            if (StringUtils.isEmpty(openId)) {
                throw new GuiguException(201, "openid不存在，登录失败");
            }

        } catch (WxErrorException e) {
            throw new GuiguException(201, "微信内部处理异常，登录失败");
        }
        //先查询一下缓存(token没过期直接拿)
        String refreshTokenKey = RedisConstant.USER_LOGIN_KEY_PREFIX + "refresh:" + openId;
        String fromRedisJwt = redisTemplate.opsForValue().get(refreshTokenKey);
        if (!StringUtils.isEmpty(fromRedisJwt)) {
            HashMap<String, Object> map = new HashMap<>();
            map.put("token", fromRedisJwt);
            map.put("refreshTokenKey", fromRedisJwt);
            return map;
        }


        //查询数据库是否存在
        LambdaQueryWrapper<UserInfo> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(UserInfo::getWxOpenId, openId);
        UserInfo userInfo = userInfoMapper.selectOne(queryWrapper);
        if (null == userInfo) {
            // 注册
            userInfo = new UserInfo();
            userInfo.setWxOpenId(openId);
            userInfo.setNickname("昵称-yyj-【" + System.currentTimeMillis() + "】");
            userInfo.setAvatarUrl("https://oss.aliyuncs.com/aliyun_id_photo_bucket/default_handsome.jpg"); //初始化一个用户的头像
            userInfo.setIsVip(0);
            userInfo.setVipExpireTime(new Date());
            userInfo.setGender(0);
            userInfo.setIntro("简介");

            int insert = userInfoMapper.insert(userInfo);
            //todo 初始化用户的账户信息
            rabbitService.sendMessage(MqConst.EXCHANGE_ACCOUNT, MqConst.ROUTING_USER_REGISTER, userInfo.getId().toString());

            log.info("注册:{}", insert > 0 ? "success" : "false");
        }


        //自定义登录信息
        String jsonWebToken = getJsonWebToken(openId, userInfo.getId());

        //将jwt令牌存储到分布式组件redis中
        //双token:一个时间长,一个时间段
        //用来对受保护资源进行保护
        String accessTokenKey = RedisConstant.USER_LOGIN_KEY_PREFIX + openId;
        //刷新AccessToken
        refreshTokenKey = RedisConstant.USER_LOGIN_KEY_PREFIX + "refresh" + openId;
        redisTemplate.opsForValue().set(accessTokenKey, jsonWebToken, 15, TimeUnit.DAYS);
        redisTemplate.opsForValue().set(refreshTokenKey, jsonWebToken, 30, TimeUnit.DAYS);


        //将自定义信息返回给前端
        HashMap<String, Object> map = new HashMap<>();
        map.put("token", jsonWebToken);
        map.put("refreshTokenKey", jsonWebToken);
        //session作用域下(自己放 自己取) 别人获取不到

        return map;
    }

    private String getJsonWebToken(String openId, Long userId) {
        JSONObject paylodMap = new JSONObject();
        paylodMap.put("openId", openId);
        paylodMap.put("userId", userId);
        Jwt encode = JwtHelper.encode(paylodMap.toString(), rsaSigner);
        String jsonWebToken = encode.getEncoded();
        return jsonWebToken;
    }

    @Override
    public Map<String, Object> refreshToken() {
        //获取请求对象
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        //获取请求对象
        HttpServletRequest request = requestAttributes.getRequest();
        //获取请求头对象
        String refreshToken = request.getHeader("token");
        //用jwt验签和解析载荷
        Jwt jwt = JwtHelper.decodeAndVerify(refreshToken, new RsaVerifier(PublicConstant.PUBLIC_KEY));
        String claims = jwt.getClaims();
        Map map = JSONObject.parseObject(claims, Map.class);
        String openId = (String) map.get("openId");
        Long userId = (Long) map.get("userId");

        //判断refreshtoken是否过期
        //如果过期,用户登录显示失败信息
        String refreshTokenKey = RedisConstant.USER_LOGIN_KEY_PREFIX + "refresh" + openId;
        String dataFromRedis = redisTemplate.opsForValue().get(refreshTokenKey);
        if (!StringUtils.isEmpty(dataFromRedis)) {
            //如果没过期生成一个新的令牌
            String jsonWebToken = getJsonWebToken(openId, userId);
            String accessTokenKey = RedisConstant.USER_LOGIN_KEY_PREFIX + openId;
            redisTemplate.opsForValue().set(accessTokenKey, jsonWebToken, 15, TimeUnit.DAYS);
            redisTemplate.opsForValue().set(refreshTokenKey, jsonWebToken, 30, TimeUnit.DAYS);

            Map<String, Object> refreshmap = new HashMap<>();
            refreshmap.put("token", jsonWebToken);
            refreshmap.put("refreshTokenKey", jsonWebToken);
            return refreshmap;
        }
        throw new GuiguException(ResultCodeEnum.LOGIN_AUTH);
    }

    @Override
    public UserInfoVo getUserInfo(Long userId) {
        //1.获取用户id
//        Long userId = AuthContextHolder.getUserId();
        //2.查询用户表
        UserInfo userInfo = userInfoMapper.selectById(userId);
        if (null == userInfo) {
            throw new GuiguException(201, "用户信息不存在");
        }
        //将userinfo转成userinfovo返回
        UserInfoVo userInfoVo = new UserInfoVo();
        BeanUtils.copyProperties(userInfo, userInfoVo);

        return userInfoVo;
    }

    @Override
    public void updateUserInfo(UserInfoVo userInfoVo) {
        //先获取原信息
        Long userId = AuthContextHolder.getUserId();

        UserInfo userInfo = userInfoMapper.selectById(userId);
        if (null == userInfo) {
            throw new GuiguException(201, "用户不存在");

        }
        userInfo.setNickname(userInfoVo.getNickname());
        userInfo.setAvatarUrl(userInfoVo.getAvatarUrl());
        userInfoMapper.updateById(userInfo);
    }

    @Override
    public Map<Long, String> getUserPaidAlbumTrackList(Long userId, Long albumId) {
        LambdaQueryWrapper<UserPaidTrack> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(UserPaidTrack::getUserId, userId);
        queryWrapper.eq(UserPaidTrack::getAlbumId, albumId);
        queryWrapper.eq(UserPaidTrack::getIsDeleted, 0);
        //查询用户买过的专辑声音列表
        List<UserPaidTrack> userPaidTracks = userPaidTrackMapper.selectList(queryWrapper);
        //转成map
        if (CollectionUtils.isEmpty(userPaidTracks)) {
            return new HashMap<>();
        }
        Map<Long, String> map = userPaidTracks.stream().collect(Collectors.toMap(UserPaidTrack::getTrackId, v -> "1"));
        return map;
    }

    @Override
    public Boolean getUserPaidAlbum(Long userId, Long albumId) {
        LambdaQueryWrapper<UserPaidAlbum> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(UserPaidAlbum::getUserId, userId);
        queryWrapper.eq(UserPaidAlbum::getAlbumId, albumId);
        queryWrapper.eq(UserPaidAlbum::getIsDeleted, 0);
        UserPaidAlbum userPaidAlbum = userPaidAlbumMapper.selectOne(queryWrapper);
        return userPaidAlbum != null;
    }

    //todo 收藏和取消收藏
    @Override
    public Boolean collect(Long trackId) {
        Long userId = AuthContextHolder.getUserId();
        //构建对象
        Criteria criteria = Criteria.where("userId").is(userId).and("trackId").is(trackId);
        Query query = new Query(criteria);

        //查询
        String collectionName = MongoUtil.getCollectionName(MongoUtil.MongoCollectionEnum.USER_COLLECT, userId);
        long count = mongoTemplate.count(query, UserCollect.class, collectionName);

        if (count == 0) {
            UserCollect userCollect = new UserCollect();
            //这段代码的作用是为 userCollect 对象的 id 属性设置一个唯一的标识符 ObjectId.get()：生成一个新的
            userCollect.setUserId(userId);
            userCollect.setTrackId(trackId);
            userCollect.setCreateTime(new Date());
            mongoTemplate.save(userCollect, collectionName);
            //更新收藏量
            TrackStatMqVo trackStatMqVo = prepareCollectMsgDto(trackId, 1);
            rabbitService.sendMessage(MqConst.EXCHANGE_TRACK, MqConst.ROUTING_TRACK_STAT_UPDATE, JSONObject.toJSONString(trackStatMqVo));
            return true;
        } else {
            mongoTemplate.remove(query, collectionName);
            //更新收藏量
            TrackStatMqVo trackStatMqVo = prepareCollectMsgDto(trackId, -1);
            rabbitService.sendMessage(MqConst.EXCHANGE_TRACK, MqConst.ROUTING_TRACK_STAT_UPDATE, JSONObject.toJSONString(trackStatMqVo));
            return false;
        }
    }

    //todo 收藏量
    private TrackStatMqVo prepareCollectMsgDto(Long trackId, int count) {
        TrackStatMqVo trackStatMqVo = new TrackStatMqVo();
        trackStatMqVo.setBusinessNo(UUID.randomUUID().toString().replace("-", ""));
        trackStatMqVo.setTrackId(trackId);
        trackStatMqVo.setStatType(SystemConstant.TRACK_STAT_PLAY);
        trackStatMqVo.setCount(count);
        return trackStatMqVo;
    }

    //todo 是否收藏
    @Override
    public Boolean isCollect(Long trackId) {
        //获取用户id
        Long userId = AuthContextHolder.getUserId();
        //构建一个对象
        Criteria criteria = Criteria.where("trackId").is(trackId).and("userId").is(userId);
        Query query = new Query(criteria);

        //查询
        String collectionName = MongoUtil.getCollectionName(MongoUtil.MongoCollectionEnum.USER_COLLECT, userId);
        long count = mongoTemplate.count(query, UserCollect.class, collectionName);
        return count > 0;
    }

    //todo 订阅
    @Override
    public Boolean isSubscribe(Long albumId) {
        //获取用户id
        Long userId = AuthContextHolder.getUserId();
        //构建一个对象
        Criteria criteria = Criteria.where("albumId").is(albumId).and("userId").is(userId);
        Query query = new Query(criteria);

        //查询
        String collectionName = MongoUtil.getCollectionName(MongoUtil.MongoCollectionEnum.USER_SUBSCRIBE, userId);
        long count = mongoTemplate.count(query, UserSubscribe.class, collectionName);

        return count > 0;
    }

    @Override
    public IPage<UserCollectVo> findUserCollectPage(IPage<UserCollectVo> page, Long userId) {
        //构建条件
        Criteria criteria = Criteria.where("userId").is(userId);
        //构建查询对象
        Query query = new Query(criteria);

        //排序和分页
        Sort sort = Sort.by(Sort.Direction.DESC, "updateTime");
        Long from = (page.getCurrent() - 1) * page.getSize();
        PageRequest pageRequest = PageRequest.of(from.intValue(), (int) page.getSize(), sort);
        query.with(pageRequest);
        // 4.查询（查询满足条件的总记录数）
        String collectionName = MongoUtil.getCollectionName(MongoUtil.MongoCollectionEnum.USER_COLLECT, userId);
        long total = mongoTemplate.count(query.limit(-1), UserCollect.class, collectionName);

        //查询数据
        List<UserCollect> userCollects = mongoTemplate.find(query, UserCollect.class, collectionName);

        // 6.获取收藏的声音id集合
        List<Long> collect = userCollects.stream().map(UserCollect::getTrackId).collect(Collectors.toList());

        //7.查询数据库获取标题和专辑id
        if (!CollectionUtils.isEmpty(collect)) {
            Result<List<TrackListVo>> trackInfoList = albumInfoFeignClient.getTrackInfoList(collect);
            List<TrackListVo> trackListVoData = trackInfoList.getData();
            if (CollectionUtils.isEmpty(trackListVoData)) {
                throw new GuiguException(201, "远程查询声音列表失败");
            }
            //8.转为map
            Map<Long, TrackListVo> map = trackListVoData.stream().collect(Collectors.toMap(TrackListVo::getTrackId, v -> v));
            List<UserCollectVo> userCollectVoList = userCollects.stream().map(userCollect -> {
                UserCollectVo userCollectVo = new UserCollectVo();
                userCollectVo.setAlbumId(map.get(userCollect.getTrackId()).getAlbumId());
                userCollectVo.setTrackId(userCollect.getTrackId());
                userCollectVo.setCreateTime(userCollect.getCreateTime());
                userCollectVo.setTrackTitle(map.get(userCollect.getTrackId()).getTrackTitle());
                userCollectVo.setCoverUrl(map.get(userCollect.getTrackId()).getCoverUrl());
                return userCollectVo;
            }).collect(Collectors.toList());
            return page.setRecords(userCollectVoList).setTotal(total);

        }
        return page;
    }
}
